线程的概念线程是我们经常听到的一个概念,他和进程有什么关系呢
从操作系统课本里我们可能听说过,线程是一个微缩版的进程,他拥有TCB,不会被分配资源,是CPU进行调度的单位
我们其实可以更直观的理解,程序中的一个执行路线我们就可以称之为线程,也可以说是执行流
那么一个进程他的执行路线至少有一个,也就算做是一个线程,这个进程本身也可以创建分支线程,那么原来的这个进程也就叫做主线程
在Linux中,线程其实没有自己的TCB,而是和进程公用PCB,内存空间也是共享的
因此在Linux中线程其实是要比进程更加轻量化的
每个线程都拥有自己的PCB,但都是自己从主线程中写时拷贝来的
在CPU的视角来看,他的所调度的所有单位都是PCB,是同一个进程,CPU也无法分辨他运行的是线程还是进程
进程和线程对比线程其实是被CPU调度运行的的基本单位,因为一个CPU只能运行一个执行流
而进程是操作系统分配资源的基本单位,也就是说这个进程和他所属的线程内存空间全部共享,一个全局变量,不同线程之间也能共享
线程也拥有自己的特征数据,否则就无法对线程进行管理了
线程ID,栈区资源,信号屏蔽字,调度优先级
共享的...
信号的基本概念
信号递达:实际处理信号的动作
信号未决:信号从产生到递达之间的状态
信号阻塞:不会被递达的信号
信号忽略:递达的动作是忽略
阻塞和忽略的区别
阻塞指的是这个信号不会被递达,也就是不对其进行操作处理
忽略指的是这个信号可以递达,只是处理的动作是忽略
信号保存
在进程的PCB中有如下三个数据结构和信号相关
前两个是位图,后一个是数组
block位图这个位图表示哪些信号被阻塞,0或1表示是否被阻塞,某一个位置表示对应的信号
pending位图这个位图用来存储收到的信号,0或1表示是否收到,某一个位置表示对应的信号,这个位图也称之为信号集,也就是未决的情况
handler数组这个数组是一个函数指针数组,里面的内容是函数指针,下标表示收到n号信号,调用的处理方法就是对应的函数指针
SIG_DFL宏代表这个函数是默认处理函数
SIG_IGN宏代表收到这个信号后,进行忽略这个信号
信号处理
这个函数可以手动更改handler数组,让进程在捕捉到对应信号的时候调用我们指定的函数处理
1234567891011121314151617181920#include<iostr...
互斥锁现实中的锁有两种状态,打开和关闭,分别对应这资源可以被使用,和不可以被使用,我们可以通过使用钥匙对锁的状态进行改变
那么解决临界区的最简单的工具其实就是锁(mutex lock),我们称之为互斥锁
当一个进程进入临界区的时候,调用acquire函数,进行上锁,相当于不让别人再访问资源
退出临界区的时候调用release函数,来打开锁,可以让别人访问资源
一个锁只能上锁一次,因此在互斥锁中也有一个bool变量available,表示锁是否可以被上锁,可以被上锁时acquire函数就可以调用成功
这两个函数的执行必须是原子的,不允许被中断,因此通常都是硬件实现的
这种互斥锁也称之为自旋锁,主要问题是忙等待,也就是必须循环检查available是否可用
这种互斥锁可以用来解决同步问题
信号量我们之前简单了解过信号量,本质上其实就是描述资源的数量
可以用来解决互斥和同步的问题,他只能被wait和signal原语访问,也称为PV操作,分别对应着申请和释放资源两个操作
整形信号量这种就是最基础的用法,描述资源的数目
wait操作和P操作是申请操作,原理可以这样表示
12345void ...
信号及其产生与发送我们从生活中理解信号,例如各种指示灯,红绿灯之类的,我们能认识红绿灯是因为每一种不同的情况在我们大脑中已经存下了,并且根据每一种不同的情况我们也可以作出不同的反应
在操作系统中的信号也是如此,系统需要做的两个动作就是识别、处理
什么是Linux信号信号本身其实就是一种通知的形式,用户或者操作系统可以通过信号给进程发送信息,让进程来进行处理
例如前台进程可以发送一个ctrl + c指令来中断进程
我们可以使用kill -l查看进程信号表
这些个信号存在bits/signum.h文件中,大致可以分为两类,从1到31是标准信号,其余的都是POSIX实时信号,都有不同的含义
ctrl + c其实就对应着2号信号
回到上面的问题,进程想要处理信号就需要识别信号,那么进程如何识别信号,其实就是程序内置的代码可以识别+处理
信号的产生是随机的,这种随机性是在时间上随机,产生的信号随机
因此进程收到信号时可能正在处理其他事情,当收到信号也无法立即响应
那么就一定存在某种数据结构来保存收到的信号
因此信号的一生就只有三个过程,产生、保存、处理
信号的产生信号的产生有四种方式,分...
硬件实现方法之前的软件方法出现各种问题的根本原因其实是因为检查是否被占用和设置自己想要占用,这两个动作没有办法一气呵成导致的
而硬件实现的好处就是他的权限足够高,无法随意被中断
中断屏蔽方法操作系统有一个系统级的指令是中断指令,主要分为两个步骤,一个是关中断指令,另一个是开中断指令
什么意思呢,就是说是否允许被中断,关中断就之后不允许被中断,开中断就是之后可以被中断
因此我们想要确保进程访问临界区时不被其他进程打扰,只需要在临界区之前执行关中断指令,在临界区之后执行开中断指令,确保进程互斥的实现
123// 关中断;// 临界区;// 开中断;
这个方法的缺点是限制了CPU交替并发执行程序的能力,即便是时间片到也无法中断,因此系统的效率就会降低
其次这个方法其实是权限的下放,就是将系统的权限交给用户的进程,这样进程有可能会一直占用CPU
这个方法还无法限制其他CPU,只能限制一个CPU不执行相同临界区的代码
TestAndSet指令这是一条硬件指令,TS指令,这条指令是原子操作,也就是原语,不允许被中断
他的代码描述如下
123456bool TestAndSet(bool* ...
实现临界区互斥的基本方法我们说临界资源是在一段时间内只允许一个进程访问,而进程访问临界资源的那段代码称为临界区
为了防止两个进程同时进入临界区访问临界资源,我们设计的同步机制就需要满足下面的准则
空闲让进:不能占着茅坑不拉屎,当临界区空闲时可以允许另一个进程请求并进入临界区
忙则等待:不能别人上着呢冲进去也要拉,当临界区被占用时,其他请求访问的进程必须等待
有限等待:不能让等待的人憋死了,防止进程饥饿,要保证进程能在有限的时间内进入临界区
让权等待:不能占着临界区、阻塞了还得占着CPU,防止忙等待
软件实现一般这种方法都是基于全局变量的,让全局变量作为标志位
单标志法最简单的一个想法其实就是设一个bool变量,0时表示P0进程可以进入,1时表示P1进程可以进入,假设全局bool变量为flag
12345// P0进程while(flag != 0); // 当flag不为0时,说明不允许P0进入临界区,则循环等待 进入区// 访问临界资源... 临界区flag = 1; // 访问完了 要让给P1进程访问了 退出区// 其他代码 剩余区
12345// P1进程while(...
消息队列SystemV除了共享内存之外,还有一个进程间通信的方式,是消息队列
我们说一切进程间通信的方式本质其实就是让不同进程看到同一份资源
这个消息队列的本质其实就是让两个进程可以同时看到这个队列,但是管理者其实还是操作系统
进程将数据块使用接口发送给操作系统,操作系统处理好之后插入到队列中,另一个进程也可以这样进行操作,对队列进行插入操作
当操作取出的接口时,给第一个进程另一个进程的数据块,给第二个进程第一个进程的数据块
那么其实这个消息队列并非一个严谨的先进先出,他是有标志的先进先出,本质上相当于两个队列,而且也不规定单向通信
这里与之前不同的其实就是标志,就是数据块的类型
操作系统对消息队列也需要进行管理,因此也有一个描述的结构体
并且由于都是SystemV标准下的结构,接口也都差不多,这里我们不过多介绍,等之后我们会对这里进行模拟实现
信号量同步这里是操作系统相关的内容,只做简单介绍,具体还是需要去读课本
我们说进程之间是具有异步性的,说白了就是我们无法确定哪个进程先执行,哪个进程后执行
这样就会造成一些问题
例如内存共享,两个进程通信时
写进程在还没写完的时候,...
System V共享内存System V是一种进程间通信的标准,在这种标准之下,本地通信的方式一般由三种,内存共享、消息队列、信号量
这里我们主要介绍共享内存
创建内存共享
这是内存共享的系统调用
第一个参数是共享内存在内核中的标识,类似于文件标识符的地位
第二个参数是创建共享内存的大小
第三个参数有两个选项IPC_CREAT和IPC_EXCL
如果只有第一个,则使用共享内存,不存在则创建后继续使用
第二个不能单独使用,两个相或之后表示创建共享内存后使用,存在则出错返回
第三种方法主要是为了确保所使用的共享一定是新鲜的
创建成功会返回共享内存的标识符,失败返回-1,错误码表示错误原因
共享内存存在内存的共享区中,可以有很多个,是被操作系统描述组织管理的
对于第一个参数,key是需要确定唯一的,类似于手机号,你自己有,别人不能再有一模一样的,并且别人想要打给你也必须输入一模一样的
我们可以使用ftok函数进行生成,只要两个参数相同,则能生成相同的key,类似于哈希函数
需要注意的是,共享内存在进程退出时是不会主动释放的,需要用户手动释放,除非系统内核关闭
释放共享内存命令行1ip...